package com.tus.handler;

import com.tus.lock.UploadLockManager;
import com.tus.model.UploadInfo;
import com.tus.storage.FileStorage;
import com.tus.store.MetadataStore;
import com.tus.util.ChecksumValidator;
import com.tus.util.MetadataParser;
import com.tus.util.UrlUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantLock;

import static io.netty.handler.codec.http.HttpHeaderNames.*;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;


/**
 * tus上传处理器
 */
@Slf4j
public class UploadHandler implements Handler {

    private final MetadataStore metadataStore;
    private final FileStorage fileStorage;
    private final String uploadPathPrefix;
    private final UploadLockManager lockManager;

    public UploadHandler(MetadataStore metadataStore, FileStorage fileStorage, String uploadPathPrefix) {
        this.metadataStore = metadataStore;
        this.fileStorage = fileStorage;
        this.uploadPathPrefix = uploadPathPrefix;
        this.lockManager = new UploadLockManager();
    }

    private void handleOptions(ChannelHandlerContext ctx, FullHttpRequest req) {
        FullHttpResponse resp = new DefaultFullHttpResponse(HTTP_1_1, OK);
        Handler.addCors(resp);
        resp.headers().set("Tus-Resumable", "1.0.0");
        resp.headers().set("Tus-Version", "1.0.0");
        resp.headers().set("Tus-Extension", "creation,creation-with-upload,concatenation");
        resp.headers().set(CONTENT_LENGTH, 0);
        ctx.writeAndFlush(resp).addListener(ChannelFutureListener.CLOSE);
    }

    private void handleCreate(ChannelHandlerContext ctx, FullHttpRequest req) {
        String concatHeader = req.headers().get("Upload-Concat");

        // 判断是否为 final 合并上传
        if (concatHeader != null && concatHeader.trim().startsWith("final;")) {
            handleFinalUpload(ctx, req, concatHeader);
            return;
        }
        // 普通上传
        handleNormalOrPartialUpload(ctx, req);
    }

    private void handleFinalUpload(ChannelHandlerContext ctx, FullHttpRequest req, String concatHeader) {
        try {
            // 解析 final; <url1> <url2> ...
            String[] urls = concatHeader.substring(5).trim().split("\\s+");
            if (urls.length == 0) {
                sendError(ctx, BAD_REQUEST, "Invalid Upload-Concat: no partial uploads specified");
                return;
            }

            // 提取 uploadId 并验证每个分片
            long totalSize = 0;
            CopyOnWriteArrayList<String> chunkPaths = new CopyOnWriteArrayList<>();
            ConcurrentHashMap<String, Long> chunkOffsets = new ConcurrentHashMap<>();

            for (String url : urls) {
                String uploadId = extractUploadIdFromUrl(url);
                if (uploadId == null) {
                    sendError(ctx, BAD_REQUEST, "Invalid partial upload URL: " + url);
                    return;
                }

                UploadInfo partInfo = metadataStore.findByUploadId(uploadId);
                if (partInfo == null || !partInfo.isParallelUpload()) {
                    sendError(ctx, NOT_FOUND, "Partial upload not found or invalid: " + uploadId);
                    return;
                }

                // 累加分片大小
                long uploadedSize = partInfo.getCompletedChunks().stream().mapToLong(fileStorage::getChunkSize).sum();

                if (uploadedSize < partInfo.getFileSize()) {
                    sendError(ctx, PRECONDITION_FAILED, "Partial upload not completed: " + uploadId);
                    return;
                }

                totalSize += uploadedSize;
                chunkPaths.addAll(partInfo.getCompletedChunks());
                chunkOffsets.putAll(partInfo.getChunkOffsets());
            }

            // 创建 final upload
            String finalUploadId = UUID.randomUUID().toString();
            String finalFilePath = fileStorage.createFile(finalUploadId, totalSize);

            UploadInfo finalInfo = new UploadInfo();
            finalInfo.setUploadId(finalUploadId);
            finalInfo.setFileName("final_" + finalUploadId);
            finalInfo.setFileSize(totalSize);
            finalInfo.setOffset(0);
            finalInfo.setFilePath(finalFilePath);
            finalInfo.setCreatedAt(System.currentTimeMillis());
            finalInfo.setExpiresAt(finalInfo.getCreatedAt() + 7 * 24 * 3600_000L);
            finalInfo.setParallelUpload(false);
            finalInfo.setCompletedChunks(chunkPaths);
            finalInfo.setChunkOffsets(chunkOffsets);
            finalInfo.setFinalUpload(true); // 可选标记

            metadataStore.saveUpload(finalInfo);

            // 合并分片
            fileStorage.mergeChunks(chunkPaths, finalFilePath, chunkOffsets);

            // 标记完成
            metadataStore.markAsCompleted(finalUploadId, finalFilePath);

            FullHttpResponse resp = new DefaultFullHttpResponse(HTTP_1_1, CREATED);
            resp.headers().set(LOCATION, uploadPathPrefix + "/" + finalUploadId);
            resp.headers().set("Tus-Resumable", "1.0.0");
            resp.headers().set(CONTENT_LENGTH, 0);
            ctx.writeAndFlush(resp).addListener(ChannelFutureListener.CLOSE);

            log.info("Final upload created and merged: {} -> {}", concatHeader, finalUploadId);

        } catch (Exception e) {
            log.error("Failed to create final upload", e);
            sendError(ctx, INTERNAL_SERVER_ERROR, "Final upload creation failed: " + e.getMessage());
        }
    }

    private String extractUploadIdFromUrl(String url) {
        try {
            // 支持 http://.../uploads/abc 或 /uploads/abc
            int idx = url.lastIndexOf('/');
            if (idx > 0) {
                return url.substring(idx + 1);
            }
            return url;
        } catch (Exception e) {
            return null;
        }
    }

    private void handleNormalOrPartialUpload(ChannelHandlerContext ctx, FullHttpRequest req) {
        String fileSizeStr = req.headers().get("Upload-Length");
        boolean isParallel = "partial".equals(req.headers().get("Upload-Concat"));
        long fileSize;
        if (!isParallel) {
            // 普通上传：必须有 Upload-Length
            if (fileSizeStr == null) {
                sendError(ctx, BAD_REQUEST, "Upload-Length required");
                return;
            }
            try {
                fileSize = Long.parseLong(fileSizeStr);
            } catch (NumberFormatException e) {
                sendError(ctx, BAD_REQUEST, "Invalid Upload-Length");
                return;
            }
        } else {
            // partial 分片上传：Upload-Length 可选
            fileSize = -1; // 大小由分片决定
        }

        // 其余逻辑保持不变（解析元数据、创建 uploadId、存储等）
        String metadataHeader = req.headers().get("Upload-Metadata");
        Map<String, String> metadata = MetadataParser.parse(metadataHeader);
        String fileName = metadata.getOrDefault("name", "upload_" + UUID.randomUUID());

        String uploadId = UUID.randomUUID().toString();

        // 提取 checksum
        String checksumStr = metadata.get("checksum");
        String checksumAlgorithm = null;
        String checksumValue = null;

        if (checksumStr != null && checksumStr.startsWith("md5 ")) {
            checksumAlgorithm = "md5";
            // 去掉 "md5 "
            checksumValue = checksumStr.substring(4).trim();
        }
        String storagePath;

        try {
            if (isParallel) {
                storagePath = fileStorage.createChunkDirectory(uploadId);
            } else {
                storagePath = fileStorage.createFile(uploadId, fileName, fileSize);
            }
        } catch (IOException e) {
            sendError(ctx, INTERNAL_SERVER_ERROR, "Failed to initialize storage");
            return;
        }

        UploadInfo info = new UploadInfo();
        info.setUploadId(uploadId);
        info.setFileName(fileName);
        info.setFileSize(fileSize);
        info.setOffset(0);
        info.setFilePath(storagePath);
        info.setCreatedAt(System.currentTimeMillis());
        info.setExpiresAt(info.getCreatedAt() + 7 * 24 * 3600_000L);
        info.setParallelUpload(isParallel);
        info.setCompletedChunks(new CopyOnWriteArrayList<>());
        info.setChunkOffsets(new ConcurrentHashMap<>());
        // 保存 checksum 信息
        info.setChecksumAlgorithm(checksumAlgorithm);
        info.setChecksum(checksumValue);
        metadataStore.saveUpload(info);

        FullHttpResponse resp = new DefaultFullHttpResponse(HTTP_1_1, CREATED);
        //新创建的上传资源的唯一 URL
        resp.headers().set(LOCATION, uploadPathPrefix + "/" + uploadId);
        if (isParallel) {
            resp.headers().set("Upload-Concat", "partial");
        }
        resp.headers().set("Tus-Resumable", "1.0.0");
        resp.headers().set(SERVER, "TUS-Netty-Server/1.0");
        resp.headers().set(CONTENT_LENGTH, 0);

        ctx.writeAndFlush(resp).addListener(ChannelFutureListener.CLOSE);
    }

    private void handlePatch(ChannelHandlerContext ctx, FullHttpRequest req) {

        String uri = req.uri();
        String uploadId = UrlUtil.extractUploadId(uri, uploadPathPrefix);
        if (uploadId == null) {
            sendError(ctx, NOT_FOUND, "Invalid upload ID");
            return;
        }
        log.info("PATCH /uploads/{} - Headers: {}", uploadId, req.headers());
        log.info("Content-Type: {}", req.headers().get("Content-Type"));
        log.info("Upload-Offset: {}", req.headers().get("Upload-Offset"));
        log.info("Upload-Length: {}", req.headers().get("Upload-Length"));
        log.info("Tus-Resumable: {}", req.headers().get("Tus-Resumable"));
        ReentrantLock lock = lockManager.getLock(uploadId);
        lock.lock();
        try {
            UploadInfo info = metadataStore.findByUploadId(uploadId);
            if (info == null) {
                sendError(ctx, NOT_FOUND, "Upload not found");
                return;
            }

            String offsetStr = req.headers().get("Upload-Offset");
            String lengthStr = req.headers().get("Upload-Length");

            if (offsetStr == null) {
                sendError(ctx, BAD_REQUEST, "Upload-Offset required");
                return;
            }

            long chunkOffset = Long.parseLong(offsetStr);
            long chunkLength;
            // 如果客户端提供了 Upload-Length（比如首次 PATCH），可以用于校验
            if (lengthStr != null) {
                try {
                    chunkLength = Long.parseLong(lengthStr);
                    // 可选：与数据库中的 fileSize 比较，做一致性校验
                    if (info.getFileSize() != -1 && info.getFileSize() != chunkLength) {
                        sendError(ctx, HttpResponseStatus.CONFLICT, "File size mismatch");
                        return;
                    }
                } catch (NumberFormatException e) {
                    sendError(ctx, HttpResponseStatus.BAD_REQUEST, "Invalid Upload-Length");
                    return;
                }
            }
            // 如果没有提供，直接使用数据库中已保存的 fileSize
            else {
                chunkLength = info.getFileSize(); // 从元数据中获取
            }

            ByteBuf content = req.content();
//            if (content.readableBytes() != chunkLength) {
//                sendError(ctx, BAD_REQUEST, "Content length does not match Upload-Length");
//                return;
//            }

            byte[] data = new byte[content.readableBytes()];
            content.readBytes(data);

            if (info.isParallelUpload()) {
                // 并发上传：保存为独立分片
                String chunkId = UUID.randomUUID().toString().substring(0, 8);
                String chunkFileName = "chunk-" + chunkOffset + "-" + chunkId;
                String chunkPath = fileStorage.writeChunkFile(uploadId, chunkFileName, data);

                info.getCompletedChunks().add(chunkPath);
                info.getChunkOffsets().put(chunkPath, chunkOffset);

                // 更新数据库
                metadataStore.updateChunks(uploadId, info.getCompletedChunks(), info.getChunkOffsets());

                // 检查是否所有分片已上传（这里简化：假设总大小已知，累计上传量等于 fileSize）
                long totalUploaded = info.getCompletedChunks().stream().mapToLong(fileStorage::getChunkSize).sum();

                FullHttpResponse resp = new DefaultFullHttpResponse(HTTP_1_1, NO_CONTENT);
                resp.headers().set("Upload-Offset", String.valueOf(totalUploaded));
                resp.headers().set("Tus-Resumable", "1.0.0");
                resp.headers().set(CONTENT_LENGTH, 0);

                // 如果上传完成，触发合并
                if (totalUploaded >= info.getFileSize()) {
                    try {
                        String finalPath = fileStorage.createFile(uploadId, info.getFileSize());
                        info.setFilePath(finalPath);
                        fileStorage.mergeChunks(info.getCompletedChunks(), finalPath, info.getChunkOffsets());

                        // 校验
                        if (info.getChecksum() != null && !info.getChecksum().isEmpty()) {
                            boolean isValid = ChecksumValidator.validate(finalPath, info.getChecksumAlgorithm(), info.getChecksum());
                            if (!isValid) {
                                fileStorage.deleteFile(finalPath);
                                metadataStore.deleteUpload(uploadId);
                                fileStorage.cleanupChunks(uploadId); // 清理分片
                                sendError(ctx, UNPROCESSABLE_ENTITY, "Checksum validation failed");
                                return;
                            }
                            metadataStore.markAsVerified(uploadId);
                        }

                        metadataStore.markAsCompleted(uploadId, finalPath);
                        log.info("Parallel upload completed and verified:{}", uploadId);
                        lockManager.removeLock(uploadId);
                    } catch (IOException e) {
                        sendError(ctx, INTERNAL_SERVER_ERROR, "Merge failed: " + e.getMessage());
                        return;
                    }
                }

                ctx.writeAndFlush(resp).addListener(ChannelFutureListener.CLOSE);

            } else {
                // 串行上传
                if (chunkOffset != info.getOffset()) {
                    sendError(ctx, CONFLICT, "Offset mismatch");
                    return;
                }

                fileStorage.writeChunk(info.getFilePath(), info.getOffset(), data);
                long newOffset = info.getOffset() + data.length;
                metadataStore.updateOffset(uploadId, newOffset);

                if (newOffset == info.getFileSize()) {
                    if (info.getChecksum() != null && !info.getChecksum().isEmpty()) {
                        boolean isValid = ChecksumValidator.validate(info.getFilePath(), info.getChecksumAlgorithm(), info.getChecksum());
                        if (!isValid) {
                            fileStorage.deleteFile(info.getFilePath());
                            metadataStore.deleteUpload(uploadId);
                            sendError(ctx, UNPROCESSABLE_ENTITY, "Checksum validation failed");
                            return;
                        }
                        metadataStore.markAsVerified(uploadId);
                    }
                    metadataStore.markAsCompleted(uploadId);
                    log.info("Upload completed: {}", uploadId);
                    lockManager.removeLock(uploadId);
                }

                FullHttpResponse resp = new DefaultFullHttpResponse(HTTP_1_1, NO_CONTENT);
                resp.headers().set("Upload-Offset", String.valueOf(newOffset));
                resp.headers().set("Tus-Resumable", "1.0.0");
                resp.headers().set(CONTENT_LENGTH, 0);
                ctx.writeAndFlush(resp).addListener(ChannelFutureListener.CLOSE);
            }

        } catch (IOException e) {
            sendError(ctx, INTERNAL_SERVER_ERROR, "Write failed: " + e.getMessage());
        } finally {
            lock.unlock();
        }
    }

    private void handleHead(ChannelHandlerContext ctx, FullHttpRequest req) {
        String uri = req.uri();
        String uploadId = UrlUtil.extractUploadId(uri, uploadPathPrefix);
        if (uploadId == null) {
            sendError(ctx, NOT_FOUND, "Invalid upload ID");
            return;
        }

        UploadInfo info = metadataStore.findByUploadId(uploadId);
        if (info == null) {
            sendError(ctx, NOT_FOUND, "Upload not found");
            return;
        }

        FullHttpResponse resp = new DefaultFullHttpResponse(HTTP_1_1, OK);
        resp.headers().set("Upload-Length", String.valueOf(info.getFileSize()));
        resp.headers().set("Cache-Control", "no-store");
        resp.headers().set("Tus-Resumable", "1.0.0");

        long offset;
        if (info.isParallelUpload()) {
            long uploaded = info.getCompletedChunks().stream().mapToLong(fileStorage::getChunkSize).sum();
            offset = Math.min(uploaded, info.getFileSize());
        } else {
            offset = info.getOffset();
        }
        resp.headers().set("Upload-Offset", String.valueOf(offset));

        resp.headers().set(CONTENT_LENGTH, 0);
        ctx.writeAndFlush(resp).addListener(ChannelFutureListener.CLOSE);
    }

    private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status, String msg) {
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8));
        response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        log.error("接口异常{}", msg);
    }

    @Override
    public String getPathPrefix() {
        return uploadPathPrefix;
    }

    @Override
    public void execute(ChannelHandlerContext ctx, FullHttpRequest req) {
        String uri = req.uri();
        HttpMethod method = req.method();
        if (uri.equals(uploadPathPrefix) && method == HttpMethod.POST) {
            //创建一个新的上传会话
            handleCreate(ctx, req);
        } else if (uri.startsWith(uploadPathPrefix + "/") && method == HttpMethod.PATCH) {
            handlePatch(ctx, req);
        } else if (uri.startsWith(uploadPathPrefix + "/") && method == HttpMethod.HEAD) {
            handleHead(ctx, req);
        } else if (uri.startsWith(uploadPathPrefix + "/") && method == HttpMethod.OPTIONS) {
            handleOptions(ctx, req);
        }
    }
}